logo头像

学如逆水行舟

为你的应用程序增加 AppIntent 能力

为你的应用程序增加AppIntent能力

引言

AppIntent是iOS16+之后引入的框架,在iOS17+后,对AppIntent的功能又进行了进一步的增强。其提供了接口可以让我们将应用程序的某部分特定功能抽离出来,提供给Siri和Shortcuts来进行调用。通过Siri和Shortcuts这类系统服务,用户的可以更加方便的使用App提供的便捷功能。另外AppIntent也可以和应用的小组件进行交互,增强小组件的交互能力。本篇文章,将讨论AppIntent的一些实用能力,希望可以对读者有所启发,将其应用于工程项目中。

一个简单的Shortcuts示例

只要我们在项目中定义了AppIntent的子类,那么系统的快捷指令服务就会自动的注册它,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct CoffeeIntent: AppIntent {
static var title = LocalizedStringResource("接收用户输入")
static var description = IntentDescription("接收用户输入的测试Intent")

// 定义参数
@Parameter(title:"输入")
var type: String?


func perform() async throws -> some IntentResult {
guard let type, type.count > 3 else {
throw $type.needsValueError(.init("参数太短"))
}
print("处理逻辑部分")
return .result()
}
}

上面的代码直接写在应用的主工程中即可,无需做额外的处理,在系统的快捷指令中即可找到指令,如下:

执行此指令,会默认后台拉起我们的App,并执行CoffeeIntent中的perform方法,我们在其中执行要处理的逻辑即可,例如数据的处理,用户配置策略的更新等。AppIntent可以接收参数,在定义时,使用@Parameter来标记参数,在perform方法中可以对参数进行校验,如果不符合我们的要求,可以使用($参数名)内置对象调用needsValueError方法来弹出一个提示弹窗,让用户重新输入参数,效果如下:

可以看到,像系统的快捷指令中注册一个AppIntent非常简单。

AppIntent的配置介绍

AppIntent是一个协议,我们需要定义一个结构体来实现它。其中定义了一些get方法需要在结构体中提供,常用的包括:

title:设置此意图的标题。

description:设置此意图的描述信息。

openAppWhenRun:意图执行时,是否自动将应用拉起到前台。

authenticationPolicy:设置意图的执行权限,例如是否允许锁屏执行等。

isDiscoverable:设置是否能够被Shortcuts和Spotlight发现,默认为true,否则不能自动注册。

perform:具体的执行方法,实现此方法来执行具体逻辑。

在AppIntent执行时,默认会将应用在后台拉起,如果我们需要应用程序进入前台,需要设置openAppWhenRun为true。

上面的配置项都比较简单,我们着重看下perform函数,这个函数会默认在子线程处理,如果我们要进行UI操作,例如将AppIntent作为主应用的快捷入口,进行主应用的页面跳转或UI操作,则需要使用@MainActor进行标注,将其派发的主线程执行,如下:

1
2
3
4
5
6
7
8
@MainActor
func perform() async throws -> some IntentResult {
guard let type, type.count > 3 else {
throw $type.needsValueError(.init("参数太短"))
}
print("处理逻辑部分")
return .result()
}

另外,执行的结果需要返回一个实现了IntentResult的对象,因为perform方法被标记为了async,因此我们是可以在其中进行await的同步编程,等待耗时任务结束后再返回结果。上面示例代码中,返回了一个空的结果,表示执行成功。我们也可以返回一个自定义的Dialog来展示执行的结果,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@MainActor
func perform() async throws -> some ProvidesDialog & ShowsSnippetView {
guard let type, type.count > 3 else {
throw $type.needsValueError(.init("参数太短"))
}
print("处理逻辑部分")
return .result(dialog: .init("Dialog标题")) {
VStack(spacing: 10) {
Text("自定义的弹窗内容")
Text("自定义的弹窗内容")
Text("自定义的弹窗内容")
}
}
}

效果如下图:

需要注意,返回的IntentResult实例要和perform函数的返回值一致,对于Dialog类型的结果,我们甚至可以通过一个简单的SwiftUI视图来自定义内容。

AppIntent也支持进行链式的调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct CoffeeIntent: AppIntent {
static var title = LocalizedStringResource("接收用户输入")
static var description = IntentDescription("接收用户输入的测试Intent")

// 定义参数
@Parameter(title:"输入")
var type: String?

@MainActor
func perform() async throws -> some OpensIntent {
guard let type, type.count > 3 else {
throw $type.needsValueError(.init("参数太短"))
}
print("处理逻辑部分")
return .result(opensIntent: TeaIntent())
}
}

struct TeaIntent: AppIntent {
static var title = LocalizedStringResource("无用户输入")
static var description = IntentDescription("无用户输入的Intent")

@MainActor
func perform() async throws -> some IntentResult {
print("链式处理逻辑部分")
return .result()
}
}

上面的代码,当执行完成CoffeeIntent的逻辑后,会再执行TeaIntent的逻辑。

向系统搜索服务中注册Shortcuts

在iOS17后,可以向系统的搜索服务中注册核心的Shortcuts,对于这类Shortcuts,用户无需手动添加,即可在搜索服务入口处直接调用。需要定义一个结构体来实现AppShortcutsProvider协议,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct ShortcutsManager: AppShortcutsProvider {

static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: CoffeeIntent(),
phrases: [
"\(.applicationName)搜索"
],
shortTitle: "搜索",
systemImageName: "magnifyingglass"
)
AppShortcut(
intent: CoffeeIntent(),
phrases: [
"\(.applicationName)编辑"
],
shortTitle: "编辑",
systemImageName: "square.and.pencil"
)
AppShortcut(
intent: CoffeeIntent(),
phrases: [
"\(.applicationName)二维码"
],
shortTitle: "二维码",
systemImageName: "qrcode.viewfinder"
)
}
}

AppShortcutsProvide会注册一组核心的AppIntent,之后可以在搜索和快捷指令中找到这些核心的AppIntent直接使用,如下:

另外,如果需要对注册的这些Intent进行更新,则直接调用此方法即可:

1
ShortcutsManager.updateAppShortcutParameters()

一些补充

在上面的示例中,在构建AppShortcut时可以设置一个系统图片,要想知道有哪些可用的图标,可以在下面的地址中下载SF-SYMBOLS应用,其中会将所有支持的符号与对应的名称列出。

https://developer.apple.com/sf-symbols/